#include <stdio.h>
#include "csapp.h"
// Recommended max cache and object sizes 
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400

// You won't lose style points for including this long line in your code 
static const char *user_agent_hdr = "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3\r\n";
static const char *conn_hdr = "Connection: close\r\n";
static const char *prox_hdr = "Proxy-Connection: close\r\n";
static const char *host_hdr_format = "Host: %s\r\n";
static const char *requestlint_hdr_format = "GET %s HTTP/1.0\r\n";
static const char *endof_hdr = "\r\n";

typedef struct{
    char buf[MAX_OBJECT_SIZE];
    char uri[MAXLINE];

    int readcnt;
    sem_t readmutex;
    int writecnt;
    sem_t writemutex;
}Cache_block;

typedef struct{
    Cache_block cache_block[10];
    int num; 
}Cache;

Cache cache;
int readcnt;
sem_t mutex, w;

void doit(int connfd);
void parse_uri(char *uri,char *servername,char *path,int *port);
void build_http_header(char *http_header,char *servername,char *path,rio_t *client_rio);
int connect_endServer(char *servername,int port,char *http_header);
void cache_init(Cache *cache);
int cache_search(Cache *cache, char *uri, int fd);
int cache_store(char *buf, char *uri);
void *thread(void *arg);

int main(int argc,char **argv)
{
    int listenfd, *connfd;
    char hostname[MAXLINE], port[MAXLINE];
    socklen_t clientlen;
    pthread_t tid;
    struct sockaddr_storage clientaddr;
    // Check command line args 
    if (argc != 2) {
	    fprintf(stderr, "usage: %s <port>\n", argv[0]);
	    exit(1);
    }

    cache_init(&cache);
    listenfd = Open_listenfd(argv[1]);
    while (1) {
	    clientlen = sizeof(clientaddr);
        connfd = Malloc(sizeof(int));
	    *connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen); 
            Getnameinfo((SA *) &clientaddr, clientlen, hostname, MAXLINE, 
                    port, MAXLINE, 0);
        printf("Accepted connection from (%s, %s)\n", hostname, port);   
        Pthread_create(&tid, NULL, thread, connfd);                                    
    }
    return 0;
}

//handle the client HTTP transaction
void doit(int connfd)
{
    char servername[MAXLINE];
    int serverport;
    char http_header[MAXLINE];
    int serverfd;

    struct stat sbuf;
    char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
    char filename[MAXLINE];
    rio_t rio;
    rio_t rio_server;

    Rio_readinitb(&rio, connfd);
    if (!Rio_readlineb(&rio, buf, MAXLINE))  
        return;
    printf("%s", buf);
    sscanf(buf, "%s %s %s", method, uri, version);
    parse_uri(uri, servername, filename, &serverport);
    build_http_header(http_header, servername, filename, &rio);

    Cache_block block;
    if(cache_search(&cache, uri, connfd)){
        printf("in cache!\n");
        return;
    }
    serverfd = connect_endServer(servername, serverport, http_header);
    Rio_readinitb(&rio_server, serverfd);
    Rio_writen(serverfd, http_header, strlen(http_header));
    size_t n, buf_size;
    buf_size = 0;
    char tmp_cache[MAX_OBJECT_SIZE];
    while ((n = Rio_readlineb(&rio_server, buf, MAXLINE)) > 0)
    {
        buf_size += n;
        Rio_writen(connfd, buf, n);
        if (buf_size < MAX_OBJECT_SIZE)
            strcat(tmp_cache, buf);
        //printf("%s", buf);
    }
    if (buf_size < MAX_OBJECT_SIZE)
        cache_store(tmp_cache, uri);
    close(serverfd);
    printf("success!\n");
}

void build_http_header(char *http_header,char *servername,char *path,rio_t *client_rio)
{
    char request_hdr[MAXLINE], host_hdr[MAXLINE];
    sprintf(request_hdr, requestlint_hdr_format, path);
    sprintf(host_hdr, host_hdr_format, servername);
    sprintf(http_header, "%s%s%s%s%s%s", request_hdr, host_hdr, user_agent_hdr, conn_hdr, prox_hdr, endof_hdr);
    printf("%s\n", http_header);
}
//Connect to the end server
inline int connect_endServer(char *servername,int port,char *http_header){
    char portStr[100];
    sprintf(portStr,"%d",port);
    printf("port: %d\n", port);
    return Open_clientfd(servername,portStr);
}

//parse the uri to get hostname,file path ,port
void parse_uri(char *uri,char *servername,char *path,int *port)
{
    char *ptr;
    char* pos = strstr(uri,"//");
    *port = 80;
    pos = pos!=NULL? pos+2:uri;

    ptr = strstr(pos, ":");
    if(ptr != NULL){
        *ptr = '\0';
        sscanf(pos, "%s", servername);
        sscanf(ptr+1, "%d%s", port, path);
        *path = '/';
        printf("path: %s\n", path);
        return;
    }
    
    ptr = strstr(pos, "/");
    if(ptr != NULL){
        *ptr = '\0';
        sscanf(pos, "%s", servername);
        *ptr = '/';
        sscanf(ptr, "%s", path);
    }
    else{
        sscanf(uri, "%s", servername);
        strcpy(path, "/");
    }
}

void *thread(void *arg)
{
    int connfd = *((int*)arg);
    Free(arg);
    Pthread_detach(pthread_self());
    doit(connfd);                                                  
    Close(connfd);
    return NULL;
}

void cache_init(Cache *cache)
{
    cache->num = 0;
    Sem_init(&mutex, 0, 1);
    Sem_init(&w, 0, 1);
    for(int i = 0;i < 10;i++){
        Sem_init(&cache->cache_block[i].readmutex, 0, 1);
        Sem_init(&cache->cache_block[i].writemutex, 0, 1);
    }
}

int cache_search(Cache *cache, char *uri, int fd)
{
    int flag = 0;
    P(&mutex);
    readcnt++;
    if (readcnt == 1)
        P(&w);
    V(&mutex);
    for (int i = 0; i < 10; i++){
        if (!strcmp(uri, cache->cache_block[i].uri)){
            Rio_writen(fd, cache->cache_block[i].buf, strlen(cache->cache_block[i].buf));
            flag = 1;
            break;
        }
    }

    P(&mutex);
    readcnt--;
    if (readcnt == 0)
        V(&w);
    V(&mutex);
    return flag;
}

int cache_store(char *buf, char *uri)
{
    P(&w);
    strcpy(cache.cache_block[cache.num].buf, buf);
    strcpy(cache.cache_block[cache.num].uri, uri);
    if(++cache.num >= 10)
        cache.num = 0;
    V(&w);
}